package com.leyou.search.service.impl;

import com.item.dto.CategoryDTO;
import com.item.dto.SpecParamDTO;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.vo.BrandDTO;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ItemClient itemClient;

    @Override
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {


        String key = searchRequest.getKey();
        //判断是否为空
        if (StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM);
        }
        //自定义查询工具
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加查询条件
        queryBuilder.withQuery(buildBasicQuery(searchRequest));
        //添加分页条件
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1,searchRequest.getSize() ));
        //执行查询获取结果
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //页面内容
        List<Goods> content = goodsAggregatedPage.getContent();
        if (CollectionUtils.isEmpty(content)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }


        //总页数
        int totalPages = goodsAggregatedPage.getTotalPages();
        //总的元素个数
        long totalElements = goodsAggregatedPage.getTotalElements();

        return new PageResult<>(totalElements,totalPages, BeanHelper.copyWithCollection(content,GoodsDTO.class));
    }

//    聚合的实现
    @Override
    public Map<String, List<?>> queryBrandByIds(SearchRequest request) {
        Map<String, List<?>> result2 = new HashMap<>();

//        查询条件构建工具
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

//        获取查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);

//        不查询
        queryBuilder.withPageable(PageRequest.of(0,1));

//        过滤字段，什么都不要
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        // 3.聚合条件
        // 3.1.分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        // 3.2.品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        // 4.查询并解析结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        // 4.1.获取分类聚合
        LongTerms categoryTerms = aggregations.get(categoryAgg);
        // 循环解析分类的聚合分桶
        List<Long> categoryIds = categoryTerms.getBuckets()
                .stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());

        // 4.2.获取分类聚合
        LongTerms brandTerms = aggregations.get(brandAgg);
        // 循环解析分类的聚合分桶
        List<Long> brandIds = brandTerms.getBuckets()
                .stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());

//        TODO跨服务请求，查询，根据分类id和品牌id，查询对应的对象信息，然后封装返回
        List<CategoryDTO> categoryDTOS = this.itemClient.queryByIds(categoryIds);

//
        List<BrandDTO> brandDTOS = this.itemClient.queryBrandByIds(brandIds);

        result2.put("分类",categoryDTOS);
        result2.put("品牌",brandDTOS);

        //TODO 处理可搜索的规格参数，前提分类唯一

        if (categoryIds!=null&&1==categoryIds.size()){
            //第一个参数表示分类的id，第二个参数表示查询条件,第三个参数就是用来封装结果
            handlerAggSpecs(categoryIds.get(0),buildBasicQuery(request),result2);
        }

        return result2;
    }

//    抽取出来的方法
//搜索过滤，本质上也是查询的一种
    private QueryBuilder buildBasicQuery(SearchRequest request) {
//        创建一个布尔查询
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();

//        查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));

        //set中存放的时map的实体，key，过滤条件名称，value，是过滤条件的值
        Set<Map.Entry<String,String>>entrySet = request.getFilter().entrySet();

        entrySet.forEach(entry ->{
            String key = entry.getKey();

            // 分类、品牌、规格参数的key都需要做一些处理
            if ("分类".equals(key)) {
                key = "categoryId";
            } else if ("品牌".equals(key)) {
                key = "brandId";
            } else {
                key = "specs." + key + ".keyword";
            }

            // 把key和value作为过滤条件
            queryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        });
        // 构建基本的match查询
        return queryBuilder;


    }

    //处理可搜索规格参数的聚合，这个聚合一定要和，搜索，相关，聚合的可搜索规格字段的值，必须是搜索的结果中的内容，而不是所有的数据
    private void handlerAggSpecs(Long cid, QueryBuilder buildBasicQuery, Map<String, List<?>> result) {
        //首先要查询可搜索的规格参数
        //TODO 可以使用缓存，redis，
        List<SpecParamDTO> specParamDTOS = this.itemClient.querySpecParams(null, cid, true);

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //先执行查询，搜索，对搜索到的数据进行聚合，之前是对所有的数据进行聚合
        queryBuilder.withQuery(buildBasicQuery);

        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        queryBuilder.withPageable(PageRequest.of(0,1));

        //循环specParamDTOS,循环添加聚合条件

        specParamDTOS.forEach(specParam->{
            //聚合的名称为可搜索规格参数的名称
            String name = specParam.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        });

        //执行查询聚合业务
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //获取到所有的聚合
        Aggregations aggregations = goodsAggregatedPage.getAggregations();


        //循环解析聚合，根据聚合的名称（可搜索规格参数的名称）
        specParamDTOS.forEach(specParam->{
            //聚合的名称为可搜索规格参数的名称
            String name = specParam.getName();

            StringTerms stringTerms = aggregations.get(name);

            List<String> values = stringTerms.getBuckets()
                    .stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    .collect(Collectors.toList());
            //key,就是可搜索规格参数的名称也就是name，值就是获取到的集合
            result.put(name,values);
        });

    }



}
